鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起看了一點 ActiveSupport 的 source code,今天就來看一下 ActiveJob 是在幹嘛的吧,夠夠~
所謂的 background job 就是要在 Rails 處理 request 之外來執行,例如:定時清除訪客資料,這件事就不是根據每個 requests 來做的,而是每一段時間就讓系統自動去執行,而且希望這些背景作業不要影響到原本 Rails request 的運作,也有些任務是會花大量處理時間,像是檔案管理、圖片處理、影片處理等等,這就不適合在 request 期間處理,這就是 background job 的使用時機
這些 background job 的運作原理,通常會在 rails 的 process 之外,在另外開個 process 來管理 background job 的執行順序、執行狀態的控制,會根據不同的 queuing backend 的設計方式不同,下面會跟大家解釋。
我看到 ActiveJob
的第一個反應是,那 ActiveJob
和 Sidekiq
的差別是啥?,因為我之前只有用過 Sidekiq
,不自覺就會有這樣的疑問
但實際上,處理 background job 的 gem,不只有 Sidekiq
,還有 Delayed Job
和 Resque
,每個 gem 的使用方式不太一樣,使用介面更是不會一樣
我們假設不知道有 ActiveJob
的存在,今天如果專案是使用 Sidekiq
,但因為某些原因,Sidekiq
不維護了(這只是假設,不怕不怕XD),逼不得已,必須換成 Delayed Job
,那應該會很痛苦,會有很多地方要改
所以,這就是 ActiveJob
存在的意義了!
ActiveJob
提供開發者統一處理 background job 的介面,讓開發者只要使用 ActiveJob
的介面來開發,至於後端的 background gem 是使用哪個 gem 就不用太在意,優點就是開發者如果要抽換掉不同的 background gem,是很簡單的一件事,只需要在 ActiveJob
換掉幾個參數就好,但缺點也比較硬傷,就是只能使用 ActiveJob
有整合的 background gem,如果今天出了一個新的、又好用的 background gem,可是 ActiveJob
還沒整合進去的話,那你就沒辦法直接在 ActiveJob
替換掉,官方在 ActiveJob 7.0.3.1 版本有支援的 gem 有 Sidekiq, Resque, Backburner, Delayed Job, Que, queue_classic, , Sneakers, Sucker Punch, Active Job Async Job, Active Job Inline,可以參考官方文件,以 github star 來看熱門度的話,應該是 Sidekiq 最熱門,有 12k 的星星,第二。的是高 Resque,有接近 10k 的星星。
官方提供的各種 background gem 比較表
如果不特別設定 background gem 的話,那麼也沒關係,因為 ActiveJob
有自帶一個簡易的 Queueing Backend,不過有個缺點,就是它是把 job 存在 RAM 裡的(in-process queuing system),所以如果機器重開、或是不知道什麼原因導致 crash 掉,這個 job 就會遺失,如果是不那麼重要的 job 或是不那麼重要的 app 的話,那應該沒差,不過如果是要上到 production 的話,官方還是建議要指定一下 background gem,讓專業的來
$ rails generate job your_job_name
invoke test_unit
create test/jobs/your_job_name_job_test.rb
create app/jobs/your_job_name_job.rb
ActiveJob
會自動幫你的 your_job_name
加上 _job
,如果你取的名字本來就有 _job
了,那他就不會幫你加上
產生出來的 job 就會長下面這樣 (官方是用 $ rails generate job
guests_cleanup
來產生的)
class GuestsCleanupJob < ApplicationJob
queue_as :default
def perform(*guests)
# Do something later
end
end
# 馬上放到 queue 排隊,只要 queue 有空就會執行
GuestsCleanupJob.perform_later guest
# 事先塞到 queue 裡,指定執行時間
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
# 事先塞到 queue 裡,指定一段時間後執行
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
如果要指定的話,記得要在 Gemfile 先把相關的 gem 裝起來,Sidekiq 和 Resque 都是以 redis 為基礎的 queuing system,所以除了 sidekiq 和 resque 要裝之外,redis 也要裝
# config/application.rb
module YourApp
class Application < Rails::Application
# Be sure to have the adapter's gem in your Gemfile
# and follow the adapter's specific installation
# and deployment instructions.
config.active_job.queue_adapter = :sidekiq
end
end
也可以針對不同的 Job 來設定 queuing backend
class GuestsCleanupJob < ApplicationJob
self.queue_adapter = :resque
# ...
end
還有一些前置作業,需要搭配 sidekiq 的 github wiki 服用,以下我簡單列出基本且必須的設置
架設 Redis
sidekiq 預設會連 127.0.0.1:6379
當作是 redis,6379 也是 redis 的預設 port
你如果會用 docker 的話,那更簡單了,直接開個 redis container 並開出 6379 port 就可以了
$ docker run --name redis -p 6379:6379 -itd redis:latest
如果要設定別的 host、別的 port 的話,可以在 config/initializers/sidekiq.rb
(這個檔案要自己新增),更詳細的設定就參考這裡
Sidekiq.configure_server do |config|
config.redis = { url: 'redis://redis.example.com:7372/0' }
end
Sidekiq.configure_client do |config|
config.redis = { url: 'redis://redis.example.com:7372/0' }
end
架設 Sidekiq Process
還記得最前面講的,background jobs 會獨立運行於 rails 之外,所以除了原本的 rails server 要跑之外,還要再開另一個 process 執行 sidekiq 才行
執行 sidekiq(記得先把 redis 開在本地 6379 port)
$ sidekiq
`$b
.ss, $$: .,d$
`$$P,d$P' .,md$P"'
,$$$$$b/md$$$P^'
.d$$$$$$/$$$P'
$$^' `"/$$$' ____ _ _ _ _
$: ,$$: / ___|(_) __| | ___| | _(_) __ _
`b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` |
$$: ___) | | (_| | __/ <| | (_| |
$$ |____/|_|\__,_|\___|_|\_\_|\__, |
.d$$ |_|
2022-09-08T02:17:46.309Z pid=12157 tid=77t INFO: Booted Rails 7.0.3.1 application in development environment
2022-09-08T02:17:46.309Z pid=12157 tid=77t INFO: Running in ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [arm64-darwin20]
2022-09-08T02:17:46.309Z pid=12157 tid=77t INFO: See LICENSE and the LGPL-3.0 for licensing details.
2022-09-08T02:17:46.309Z pid=12157 tid=77t INFO: Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org
2022-09-08T02:17:46.309Z pid=12157 tid=77t INFO: Booting Sidekiq 6.5.6 with Sidekiq::RedisConnection::RedisAdapter options {}
2022-09-08T02:17:46.345Z pid=12157 tid=77t INFO: Starting processing, hit Ctrl-C to stop
你就可以讓你的 background job 開始跑了
試著用一開始產生的 GuestsCleanupJob
去跑跑看
class GuestsCleanupJob < ApplicationJob
queue_as :default
def perform(*guests)
File.open("#{Rails.root}/tmp/cleanup.log", "a") do |file|
file.write("#{DateTime.current}: 執行 GuestsCleanupJob,參數為 #{guests}\n")
end
end
end
我們開個 rails console 來把 Job 塞進去 queue 吧
$ rails c
3.0.0 :001 > GuestsCleanupJob.perform_later(1,2,3)
Enqueued GuestsCleanupJob (Job ID: bb7f38fb-0d66-4cfa-93e8-d7e34afd5eb5) to Sidekiq(default) with arguments: 1, 2, 3
=>
#<GuestsCleanupJob:0x000000012ff266d0
@arguments=[1, 2, 3],
@exception_executions={},
@executions=0,
@job_id="bb7f38fb-0d66-4cfa-93e8-d7e34afd5eb5",
@priority=nil,
@provider_job_id="385b37969e546b3471099adf",
@queue_name="default",
@successfully_enqueued=true,
@timezone="UTC">
這時 Job 執行後的結果已經寫入 log 了,表示已經執行完畢
# tmp/cleanup.log
2022-09-08T02:37:35+00:00: 執行 GuestsCleanupJob,參數為 [1, 2, 3]
[Optional] 把 sidekiq 的監控介面加入 routes
# config/routes.rb
require 'sidekiq/web'
Rails.application.routes.draw do
mount Sidekiq::Web => "/sidekiq"
...
end
ps. 如果你是 api mode 的話,需要加入 session 的 middleware 才行,參考這裡
然後打開 127.0.0.1:3000/sidekiq
就可以看到已經有一個已處理的任務囉~
[Advance] 如果你想要區分不同優先程度的任務的話,可以設置多個 queue,預設已經有 default
queue,你也可以加入 urgent
queue,有兩個 queue 的話,那就需要兩個 process 來執行
$ sidekiq -q default
: 這樣這個 queue 就只會執行 default 的 jobs$ sidekiq -q urgent
: 這個 queue 就只會執行 urgent 的 jobs這篇 RailsGuide 下面還有一些關於 queue 的操作細節,像是有些 job 在不同情況之下,會適合用不同的 queue 來處理,就需要動態調整 queue,例如有些付費用戶的任務會比較優先處理等等的需求,ActiveJob
還有提供 callback 使用,可能在執行 job 的前後需要做一些前處理、後處理,提供 before_enqueue
, around_enqueue
, after_enqueue
, before_perform
, around_perform
, after_perform
六種 callbacks,除了以上這些,還有一些細節的操控,就請大家需要的時候自己看文件了~
或許有用 ActiveJob
和 沒有用 ActiveJob
的差別不大,我之前是沒有透過 ActiveJob
來處理 background jobs,而是直接使用 Sidekiq
,覺得用起來也蠻直覺的,只是萬一真的有一天必須把 Sidekiq
換掉的話,那真的會是大工程;但對於小專案來說,那還真的是沒有太大差別。
今天這篇就這樣囉,明天來看跟 ActiveJob
有點關聯的東東 - ActiveMailer
,我們明天見~